﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Actor.Net.Network.Gate.Tcp
{
    public class GateTcpService : GateBaseService
    {
        private Socket acceptor { get; set; }
        private SocketAsyncEventArgs connectionArgs { get; set; }
        public GateTcpService(GateNetwork network) : base(network) { }

        public override GateChannelContext Connect(int timeoutMillisecond)
        {
            this.ConnectChannel = ConnectChannel ?? new GateTcpChannel(this, this.CreateChannelId());
            this.ConnectChannel.Connect(timeoutMillisecond);
            return this.ConnectChannel.ChannelContext;
        }

        public override async Task<GateChannelContext> ConnectAsync(CancellationToken cancellationToken)
        {
            this.ConnectChannel = ConnectChannel ?? new GateTcpChannel(this, this.CreateChannelId());
            await this.ConnectChannel.ConnectAsync(cancellationToken);
            return this.ConnectChannel.ChannelContext;
        }

        public override void Listen()
        {
            if (acceptor == null)
            {
                this.acceptor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.acceptor.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                this.connectionArgs = new SocketAsyncEventArgs();
                this.connectionArgs.Completed += this.OnAcceptComplete;
                this.acceptor.Bind(this.Network.ConnectEndPoint);
                this.acceptor.Listen(1000);
            }

            this.connectionArgs.AcceptSocket = null;
            if (this.acceptor.AcceptAsync(this.connectionArgs))
                return;

            this.OnAcceptComplete(this, this.connectionArgs);
        }

        public override void Update()
        {
            if (this.ServiceType == NetServiceType.Undefined)
                return;

            if (this.ServiceType == NetServiceType.Client)
            {
                if (this.ConnectChannel == null)
                    return;

                this.ConnectChannel.StartSend();
            }
            else
            {
                foreach (var context in ConnectingChannels.Values)
                {
                    if (context.Channel.IsDropped && context.Channel.Network.IsIsDropTimeout)
                    {
                        context.Channel.Disconnect();
                        continue;
                    }
                    context.Channel.StartSend();
                }
            }
        }

        private void OnAcceptComplete(object sender, SocketAsyncEventArgs e)
        {
            if (e.LastOperation != SocketAsyncOperation.Accept)
                return;

            if (this.acceptor == null)
                return;

            if (e.SocketError != SocketError.Success)
                return;

            GateTcpChannel channel = new GateTcpChannel(e.AcceptSocket, this, this.CreateChannelId());
            if (GateNetwork.WorkThreadId != Thread.CurrentThread.ManagedThreadId)
            {
                Action action = () => { this.ProcessConnected(channel.ChannelContext); };
                this.Network.Post(this.Network.ProcessContextAction, action);
            }
            else
            {
                this.ProcessConnected(channel.ChannelContext);
            }
            this.Listen();
        }

        internal override void ProcessConnected(GateChannelContext context)
        {
            if (GateBaseService.ChannelContexts.ContainsKey(context.ChannelId))
                return;

            if (!context.Channel.Connected)
                return;

            GateBaseService.ChannelContexts.AddOrUpdate(context.ChannelId, context, (k, v) => context);
            this.ConnectingChannels.AddOrUpdate(context.ChannelId, context, (k, v) => context);
            try
            {
                this.OnConnected?.Invoke(context);
            }
            catch (Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        internal override void ProcessDisconnected(GateChannelContext context)
        {
            if (!GateBaseService.ChannelContexts.TryRemove(context.ChannelId, out context))
                return;

            this.ConnectingChannels.TryRemove(context.ChannelId, out _);

            if (this.ServiceType == NetServiceType.Client)
            {
                try
                {
                    this.OnDisconnected?.Invoke(context);
                }
                catch (Exception ex)
                {
                    this.ProcessSocketError(context, ex);
                }
                return;
            }

            try
            {
                this.OnDisconnected?.Invoke(context);
            }
            catch (Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        public override void Dispose()
        {
            if (this.ServiceType == NetServiceType.Client)
            {
                this.ConnectChannel.Disconnect();
            }
            else
            {
                foreach(var context in this.ConnectingChannels.Values)
                {
                    context.Channel.Disconnect();
                }
            }
        }
    }
}
